 /**
 * Copyright (c) Islon Scherer 2010.
 */
parser grammar ProgmaParser;

options {
  language = Java;
  output = AST;
  superClass = ProgmaBaseParser;
  tokenVocab = ProgmaLexer;
}

tokens {
    PROGRAM;
    DEFUN;
    LAMBDA;
    PARS;
    CALL;
    JCALL;
    JFUN;
    LIST;
    TABLE;
    KEYVAL;
    META;
    SYMBOL;
    LIST_PATT;
    CTOR_PATT;
}

@header {
package progma.lang.compiler;

import static progma.lang.compiler.VarType.*;
}

program
@after {
  st.processGlobals();
}
    : module_import_stmt? fixity* {checkSymbolTableRoot();} def+
      -> ^(PROGRAM module_import_stmt? fixity* def+)
    ;

// packages and imports

module_import_stmt
    : module_stmt NL!
    | import_stmt NL!
    ;

module_stmt
    : MODULE VAR {checkVar($VAR, QUALIFIED_ID); st.push($VAR);} (import_stmt)?
      -> ^(MODULE VAR import_stmt?)
    ;

import_stmt
    : IMPORT LBRACK vars+=package_name
      (SEMICOLON vars+=package_name)* RBRACK
      -> ^(IMPORT $vars+)
    ;

package_name
    : VAR {checkVar($VAR, QUALIFIED_ID);}
    | VARWILD
    ;

// fixity

fixity
    : INFIX NUMBER OPERATOR NL
      {checkValidFixityDef($NUMBER); addFixity($NUMBER, $OPERATOR);}
      -> ^(INFIX OPERATOR NUMBER)
    ;

// data definition

def
    : typedef
    | defvar NL!
    | defun NL!
    ;

typedef
    : classdef NL!
    ;

classdef
    : (md+=metadata)* CLASS name=VAR {checkClassName($VAR);} (LBRACK fls+=varname (SEMICOLON fls+=varname)* RBRACK)?
      -> ^(CLASS $name $md* $fls*)
    ;

varname
    : VAR {checkVar($VAR, VARI);}
    ;

defvar
    : (md2+=metadata)* (name2=VAR {checkVar($VAR, VARI); st.addVar($VAR);} | name=OPERATOR) COLONEQUALS expr
      -> ^(COLONEQUALS $name2 $md2* expr)
    ;

defun
@after {
  st.pop();
}
    : (md+=metadata)* name=VAR {checkVar(name, VARI); st.addVar(name); st.push(name);} COLON
      (pars+=VAR {checkVar(pars, VARI); st.addVar(pars);} (COMMA pars+=VAR {checkVar(pars, VARI); st.addVar(pars);})*)?
      EQUALS fun_body
      -> ^(DEFUN $name $md* ^(PARS $pars*) fun_body)
    | (md+=metadata)* op=OPERATOR {st.addVar(op);} COLON
      v1=VAR {checkVar(v1, VARI); st.addVar(v1);} COMMA v2=VAR {checkVar(v2, VARI); st.addVar(v2);} EQUALS fun_body
      -> ^(DEFUN $op $md* ^(PARS $v1 $v2) fun_body)
    ;

fun_body
    : expr -> expr
    | NL? LCURLY NL? (statements)? NL? RCURLY -> statements?
    ;

// lambda

lambda
@after {
  st.pop();
}
    : NL? LAMBDASTART {st.push("\$lambda");} NL? (pars+=VAR {checkVar(pars, VARI);} (COMMA pars+=VAR {checkVar(pars, VARI);})* NL? ARROW NL?)?
      sts=statements NL? RCURLY
      -> ^(LAMBDA ^(PARS $pars*) $sts)
    ;

// statements

statements
    : statement ((SEMICOLON! (NL!)? | NL!) statement)*
    ;

statement
options {
  backtrack = true;
}
    : defvar
    | assignment
    | defun
    | expr
    | while_stmt
    | try_stmt
    ;

assignment
    : (VAR {st.addGlobal($VAR);} | name=OPERATOR) EQUALS expr
      -> ^(EQUALS VAR expr)
    ;

while_stmt
@after {
  st.pop();
}
    : WHILE test=expr DO  {st.push("\$while");} suite
      -> ^(WHILE $test suite)
    ;

suite
    : expr
    | NL? LCURLY NL? sts=statements NL? RCURLY
      -> $sts
    ;

try_stmt
    : TRY NL? LCURLY NL? {st.push("\$try");} (sts=statements)? NL? {st.pop();} RCURLY
      (NL ca+=catch_stmt)* (NL finally_stmt)?
      -> ^(TRY $sts? $ca* finally_stmt?)
    ;

catch_stmt
    : CATCH VAR {checkVar($VAR, VARI);} lambda
      -> ^(CATCH VAR lambda)
    ;

finally_stmt
    : FINALLY NL? LCURLY {st.push("\$finally");} NL? (statements)? NL? {st.pop();} RCURLY
      -> ^(FINALLY statements)
    ;

// pattern matching

match_stmt
    : MATCH ex=expr NL? LCURLY NL?
      cases+=case_stmt ((NL | SEMICOLON) cases+=case_stmt)* NL? RCURLY
      -> ^(MATCH $ex $cases+)
    ;

case_stmt
@after {
  st.pop();
}
    : CASE case_ctor ARROW {st.push("\$case");} suite
      -> ^(CASE case_ctor suite)
    ;

case_ctor
    : NUMBER
    | STRING
    | WILD
    | list_patt
    | ctor_patt
    ;

list_patt
    : LBRACK RBRACK
      -> ^(LIST_PATT)
    | LBRACK vs+=varname (COLON vs+=varname)? RBRACK
      -> ^(LIST_PATT $vs+)
    ;

ctor_patt
    : cl=VAR {checkClassName($cl);} (obj=varname)?
      -> ^(CTOR_PATT $cl $obj?)
    ;

// expressions

expr
    : precedence0
    ;

infix_fun
options {
  backtrack = true;
}
    : p1=precedence0 BACKQUOTE f=VAR BACKQUOTE p2=precedence0
      -> ^(CALL $f $p1 $p2)
    | precedence0
    ;

precedence0
    : precedence1 ({checkFixity(input.LT(1), 0)}? => OPERATOR^ precedence1)*
    ;

precedence1
    : precedence2 ({checkFixity(input.LT(1), 1)}? => OPERATOR^ precedence2)*
    ;

precedence2
    : precedence3 ({checkFixity(input.LT(1), 2)}? => OPERATOR^ precedence3)*
    ;

precedence3
    : precedence4 ({checkFixity(input.LT(1), 3)}? => OPERATOR^ precedence4)*
    ;

precedence4
    : precedence5 ({checkFixity(input.LT(1), 4)}? => OPERATOR^ precedence5)*
    ;

precedence5
    : precedence6 ({checkFixity(input.LT(1), 5)}? => OPERATOR^ precedence6)*
    ;

precedence6
    : precedence7 ({checkFixity(input.LT(1), 6)}? => OPERATOR^ precedence7)*
    ;

precedence7
    : precedence8 ({checkFixity(input.LT(1), 7)}? => OPERATOR^ precedence8)*
    ;

precedence8
    : precedence9 ({checkFixity(input.LT(1), 8)}? => OPERATOR^ precedence9)*
    ;

precedence9
    : function ({checkFixity(input.LT(1), 9)}? => OPERATOR^ function)*
    ;

function
options {
  backtrack = true;
}
    : p1=literals BACKQUOTE fu=VAR BACKQUOTE p2=literals
      -> ^(CALL $fu $p1 $p2)
    | f=literals COLON pars+=expr (COMMA pars+=expr)*
      -> ^(CALL $f $pars+)
    | f1=literals COLON
      -> ^(CALL $f1)
    | literals
    ;

literals
options {
  backtrack = true;
}
    : java_call
    | atom
    | VAR
    | table
    | list
    | lambda
    | if_expr
    | LPAREN expr RPAREN -> expr
    | LPAREN OPERATOR RPAREN -> OPERATOR
    | match_stmt
    ;

java_call
    : (obj=VAR | obj=STRING) (funs+=pars_java_call)+
      -> ^(JCALL $obj $funs+)
    ;

pars_java_call
    : DOT name=VAR (COLON pars+=expr (COMMA pars+=expr)*)?
      -> ^(JFUN $name $pars*)
    | DOT LPAREN name=VAR (COLON pars+=expr (COMMA pars+=expr)*)? RPAREN
      -> ^(JFUN $name $pars*)
    ;

if_expr
    : IF test=expr THEN {st.push("\$then");} th=suite {st.pop();}
      (ELSE {st.push("\$else");} el=suite)? {st.pop();}
      -> ^(IF $test ^(THEN $th) $el?)
    ;

// literals and syntax-supported types

list
    : LBRACK (ex+=expr (SEMICOLON ex+=expr)*)? RBRACK
      -> ^(LIST $ex*)
    ;

table
    : LBRACK ARROW RBRACK
      -> ^(TABLE)
    | LBRACK tu+=table_tuple (SEMICOLON tu+=table_tuple)* RBRACK
      -> ^(TABLE $tu+)
    ;

table_tuple
    : k=expr ARROW v=expr -> ^(KEYVAL $k $v)
    ;

atom
    : NUMBER
    | STRING
    | CHAR
    | BOOL
    | NULL
    ;

// metadata

metadata
    : AT VAR {checkVar($VAR, VARI);} (LPAREN vs+=simple_var (COMMA vs+=simple_var)* RPAREN)? NL?
      -> ^(META VAR $vs*)
    ;

simple_var
    : atom
    | VAR
    ;